#ifndef RAY_TRACE_PRO_RAY_H
#define RAY_TRACE_PRO_RAY_H

#include "VectorMath.h"

namespace RayTracePro
{
	using namespace GxLibMath;

	class Ray
	{
	public:
		Vec3 Origin; 
		Vec3 Direction, ReciprocalDirection;
		float tMax;
		unsigned int RayId;
		Ray()
		{}
	};

	class DifferentialGeometry
	{
	public:
		Vec3 Position;
		Vec3 Normal;
		Vec2 UV;
		int MaterialId;
		float dUdx, dUdy, dVdx, dVdy;
		int dNdxContinuation;
		Vec3 dNdx, dNdy; // Lazy computation
	};

	class RayDifferential
	{
	public:
		Vec3 dPdx;
		Vec3 dPdy;
		Vec3 dDdx;
		Vec3 dDdy;
		RayDifferential() 
		{
		}
	};

	inline void TransferRayDifferential(RayDifferential & rayDiff, const Ray & r, const Vec3 & normal, float t)
	{
		Vec3 tmpVec;
		float invDdotN = 1.0f/Vec3::Dot(r.Direction, normal);
		Vec3::Scale(tmpVec, rayDiff.dDdx, t);
		rayDiff.dPdx += tmpVec;
		float dtdx = -Vec3::Dot(rayDiff.dPdx, normal)*invDdotN;
		Vec3::Scale(tmpVec, r.Direction, dtdx);
		rayDiff.dPdx += tmpVec;

		Vec3::Scale(tmpVec, rayDiff.dDdy, t);
		rayDiff.dPdy += tmpVec;
		float dtdy = -Vec3::Dot(rayDiff.dPdy, normal)*invDdotN;
		Vec3::Scale(tmpVec, r.Direction, dtdy);
		rayDiff.dPdy += tmpVec;
	}

	inline Ray TransformRay(const Matrix4 & transform, const Ray & ray)
	{
		Ray transformedRay;
		transform.TransformNormal(transformedRay.Direction, ray.Direction);
		transform.Transform(transformedRay.Origin, ray.Origin);
		transformedRay.tMax = ray.tMax;
		transformedRay.ReciprocalDirection.x = 1.0f / transformedRay.Direction.x;
		transformedRay.ReciprocalDirection.y = 1.0f / transformedRay.Direction.y;
		transformedRay.ReciprocalDirection.z = 1.0f / transformedRay.Direction.z;
		return transformedRay;
	}

	inline DifferentialGeometry TransformIntersection(const Matrix4 & transform, const DifferentialGeometry & inter)
	{
		DifferentialGeometry result;
		result = inter;
		transform.TransformNormal(result.Normal, inter.Normal);
		transform.Transform(result.Position, inter.Position);
		transform.TransformNormal(result.dNdx, inter.dNdx);
		transform.TransformNormal(result.dNdy, inter.dNdy);
		return result;
	}
}

#endif